﻿using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using CacheProvider;
using CacheProvider.Classic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using CacheProviderEntities;

namespace ClassicCacheProviderUnitTests
{


    /// <summary>
    ///This is a test class for ClassicCacheProviderTest and is intended
    ///to contain all ClassicCacheProviderTest Unit Tests
    ///</summary>
    [TestClass()]
    public class ClassicCacheProviderTest
    {
        private TestContext testContextInstance;

        private ClassicCacheProvider _target;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        //Use TestInitialize to run code before running each test
        [TestInitialize()]
        public void TestInitialize()
        {
            _target = new ClassicCacheProvider();
            var nameValueConfig = new NameValueCollection();
            nameValueConfig.Add("defaultExpirationTimeInMS", "60000");
            _target.Initialize("ClassicCacheProvider", nameValueConfig);
            _target.Remove("TestKey");
        }


        [TestMethod()]
        public void FirstTest()
        {
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBName = "AdventureWorks",
                DBConnectionString = GetConnectionString(),
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add(GetSQL());

            string key = "TestKey";
            var dataset = _target.Get(key) as DataSet;

            if (dataset == null)
            {
                dataset = GetDataFromDB(dependencyInfo);

                bool actual = _target.Put(key, dataset, dependencyInfo);
                Assert.IsTrue(actual);

                var actualDataset = _target.Get(key) as DataSet;
                Assert.AreEqual(dataset.Tables[0].Rows.Count, actualDataset.Tables[0].Rows.Count);
            }
        }

        [TestMethod()]
        public void NameTest()
        {
            string actual;
            actual = _target.Name;
            Assert.AreEqual("ClassicCacheProvider", actual, false);
        }

        [TestMethod()]
        public void IsRegionSupportedTest()
        {
            bool actual;
            actual = _target.IsRegionSupported;
            Assert.IsFalse(actual);
        }

        [TestMethod()]
        public void IsConcurrencySupportedTest()
        {
            bool actual;
            actual = _target.IsConcurrencySupported;
            Assert.IsFalse(actual);
        }

        [TestMethod()]
        public void IsCacheTagSupportedTest()
        {
            bool actual;
            actual = _target.IsCacheTagSupported;
            Assert.IsFalse(actual);
        }

        [TestMethod()]
        public void DescriptionTest()
        {
            string actual;
            actual = _target.Description;
            Assert.AreEqual("Cache Provider which wraps System.Web.Caching.Cache", actual, false);
        }

        [TestMethod()]
        public void DefaultExpirationTimeInMilliSecondsTest()
        {
            //Arrange            
            long expected = 60000; //Is set in the TestInitialize method
            long actual;

            //Act
            actual = _target.DefaultExpirationTimeInMilliSeconds;

            //Assert
            Assert.AreEqual(expected, actual);
        }

        [TestMethod()]
        [ExpectedException(typeof(NotImplementedException))]
        public void RemoveAllTest()
        {
            _target.RemoveAll();
        }

        [TestMethod()]
        public void RemoveTest()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            bool actual;
            actual = _target.Put(key, value);
            Assert.IsTrue(actual);
            Assert.AreEqual(value, _target.Get<string>(key));

            //Act
            actual = false;
            actual = _target.Remove(key);

            //Assert
            Assert.IsTrue(actual);
            Assert.IsNull(_target.Get<string>(key));
        }

        [TestMethod()]
        public void PutTest_SqlDependency()
        {
            //Arrange
            var key = "TestKey";
            var value = GetProducts();
            bool expected = true;
            bool actual;
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBName = "AdventureWorks",
                DBConnectionString = GetConnectionString(),
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add(GetSQL());

            //Act
            actual = _target.Put(key, value, dependencyInfo);

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = _target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);

            //Change the related data
            this.UpdateProduct();

            Thread.Sleep(2000); //wait for a few seconds
            retrievedValue = _target.Get(key) as List<Product>;
            Assert.IsNull(retrievedValue);
        }

        [TestMethod()]
        public void PutTest_Simple()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";

            bool expected = true;
            bool actual;

            //Act
            actual = _target.Put(key, value);


            //Assert
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(value, _target.Get<string>(key));

            //Clean-up
            _target.Remove(key);
        }

        [TestMethod()]
        public void PutTest_SqlDependency_Timeout_TestingTimeout()
        {
            //Arrange
            var key = "TestKey";
            var value = GetProducts();
            TimeSpan timeout = TimeSpan.FromSeconds(10);
            var stopwatch = new Stopwatch();
            bool expected = true;
            bool actual;
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBName = "AdventureWorks",
                DBConnectionString = GetConnectionString(),
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add(GetSQL());

            //Act
            actual = _target.Put(key, value, dependencyInfo, timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = _target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);

            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;


            retrievedValue = _target.Get(key) as List<Product>;

            Assert.IsNull(retrievedValue);

            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (_target.Put(key, newProductList, dependencyInfo, timeout))
                {
                    retrievedValue = _target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not updating even after 2 seconds latency");
            }
        }

        [TestMethod()]
        public void PutTest_SqlDependency_Timeout_TestingDependencyChange()
        {
            //Arrange
            var key = "TestKey";
            var value = GetProducts();
            TimeSpan timeout = TimeSpan.FromSeconds(10);
            var stopwatch = new Stopwatch();
            bool expected = true;
            bool actual;
            var dependencyInfo = new DependencyInfo(CacheDependencyTypes.SQLDependency)
            {
                DBName = "AdventureWorks",
                DBConnectionString = GetConnectionString(),
                IsSQLSelectQueryBased = true
            };

            dependencyInfo.SelectQueries.Add(GetSQL());

            //Act
            actual = _target.Put(key, value, dependencyInfo, timeout);

            //Assert
            Assert.AreEqual(expected, actual);
            var retrievedValue = _target.Get(key) as List<Product>;
            CollectionAssert.AreEqual(value, retrievedValue);

            //Change the related data
            this.UpdateProduct();

            Thread.Sleep(2000); //wait for a few seconds
            retrievedValue = _target.Get(key) as List<Product>;

            Assert.IsNull(retrievedValue);

            if (retrievedValue == null)
            {
                var newProductList = GetProducts();
                if (_target.Put(key, newProductList, dependencyInfo, timeout))
                {
                    retrievedValue = _target.Get(key) as List<Product>;
                    CollectionAssert.AreEqual(newProductList, retrievedValue);
                }
            }
            else
            {
                Assert.Fail("Cache is not updating even after 2 seconds latency");
            }
        }

        [TestMethod()]
        public void PutTest_Timeout()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            TimeSpan timeout = TimeSpan.FromSeconds(10);
            var stopwatch = new Stopwatch();
            bool expected = true;
            bool actual;

            //Act
            actual = _target.Put(key, value, timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(value, _target.Get<string>(key));

            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;
            Thread.Sleep(1000);

            Assert.IsNull(_target.Get(key));
        }

        [TestMethod()]
        public void PutTest_Timeout_WithSameKeyAgain()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            TimeSpan timeout = TimeSpan.FromSeconds(10);
            var stopwatch = new Stopwatch();
            bool expected = true;
            bool actual;
            actual = _target.Put(key, value, timeout);

            //Act            
            _target.Put(key, value + " Updated", timeout);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(value + " Updated", _target.Get<string>(key));

            while (stopwatch.ElapsedMilliseconds < timeout.TotalMilliseconds)
                ;

            Thread.Sleep(1000); //wait a sec

            Assert.IsNull(_target.Get(key));
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentException), "Parameter: useDefaultExpiration must be set to true when using this overload.  Else, use Add(string key, object value, long ttlInMilliSeconds)")]
        public void AddTest_DefaultExpiration_Negative()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            bool actual;
            var stopwatch = new Stopwatch();
            bool useDefaultExpiration = false;

            //Act
            actual = _target.Add(key, value, useDefaultExpiration);
        }

        [TestMethod()]
        public void AddTest_DefaultExpiration()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            bool actual;
            var stopwatch = new Stopwatch();
            bool useDefaultExpiration = true;
            bool expected = true;

            //Act
            actual = _target.Add(key, value, useDefaultExpiration);
            stopwatch.Start();

            //Assert
            Assert.AreEqual(expected, actual);
            Assert.AreEqual(value, _target.Get<string>(key));

            //wait for the default timeout to expire
            while (stopwatch.ElapsedMilliseconds < _target.DefaultExpirationTimeInMilliSeconds)
                ;

            Thread.Sleep(1000);

            Assert.IsNull(_target.Get(key));
        }

        [TestMethod()]
        public void AddTest_Simple_WithSameKeyAgain()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            bool actual;
            _target.Add(key, value);

            //Act
            //Trying to add wit the same key and an updated value.
            actual = _target.Add(key, value + " Updated");

            //Assert
            Assert.IsFalse(actual);
            Assert.AreEqual(value, _target.Get(key));

            //clean-up
            _target.Remove(key);
        }

        [TestMethod()]
        public void AddTest_Simple()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            bool actual;

            //Act
            actual = _target.Add(key, value);

            //Assert
            Assert.IsTrue(actual);
            Assert.AreEqual(value, _target.Get(key));

            //clean-up
            _target.Remove(key);
        }

        [TestMethod()]
        public void AddTest_TimeoutAsTimeSpan()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            TimeSpan timeSpan = TimeSpan.FromMilliseconds(10 * 1000);
            bool actual;
            var stopwatch = new Stopwatch();

            //Act
            actual = _target.Add(key, value, timeSpan);
            stopwatch.Start();

            //Assert
            Assert.IsTrue(actual);
            Assert.AreEqual(value, _target.Get(key));

            while (stopwatch.ElapsedMilliseconds < timeSpan.TotalMilliseconds)
            {
                //to prove that the cache expiration is not Sliding but absolute.
                _target.Get(key);
            }

            Thread.Sleep(1000);

            Assert.IsNull(_target.Get(key));
        }

        [TestMethod()]
        public void AddTest_TimeoutInMilliseconds()
        {
            //Arrange
            var key = "TestKey";
            var value = "Test value";
            long ttlInMilliSeconds = 10 * 1000;
            bool actual;
            var stopwatch = new Stopwatch();

            //Act
            actual = _target.Add(key, value, ttlInMilliSeconds);
            stopwatch.Start();

            //Assert
            Assert.IsTrue(actual);
            Assert.AreEqual(value, _target.Get(key));

            while (stopwatch.ElapsedMilliseconds < ttlInMilliSeconds)
                ;

            Thread.Sleep(1000);

            Assert.IsNull(_target.Get(key));

        }


        #region Helper Methods

        private static string GetConnectionString()
        {
            if (Environment.MachineName.Equals("IN8202039D"))
                return @"Data Source=SRVBLRCAF01\MSBLRTCTD01;Integrated Security=true;" +
                  "Initial Catalog=AdventureWorks;";

            return @"Data Source=RAHUL-PC\SQL2008EXPRESS;Integrated Security=true;" +
              "Initial Catalog=AdventureWorks;";
        }

        private static string GetSQL()
        {
            return "SELECT Production.Product.ProductID, " +
            "Production.Product.Name, " +
            "Production.Location.Name AS Location, " +
            "Production.ProductInventory.Quantity " +
            "FROM Production.Product INNER JOIN " +
            "Production.ProductInventory " +
            "ON Production.Product.ProductID = " +
            "Production.ProductInventory.ProductID " +
            "INNER JOIN Production.Location " +
            "ON Production.ProductInventory.LocationID = " +
            "Production.Location.LocationID " +
            "WHERE ( Production.ProductInventory.Quantity <= 100 ) " +
            "ORDER BY Production.ProductInventory.Quantity, " +
            "Production.Product.Name;";
        }

        private void UpdateProduct()
        {
            string sql = "update Production.Product" +
                         " set Name='Half-Finger Gloves, M Updated'" +
                         " where ProductID=859";
            using (var connection = new SqlConnection(GetConnectionString()))
            using (var sqlCommand = new SqlCommand(sql, connection))
            {
                connection.Open();
                sqlCommand.ExecuteNonQuery();
            }
        }

        private List<Product> GetProducts()
        {
            List<Product> products = new List<Product>();
            using (var connection = new SqlConnection(GetConnectionString()))
            using (var sqlCommand = new SqlCommand(GetSQL(), connection))
            {
                connection.Open();
                IDataReader reader = sqlCommand.ExecuteReader();
                int productIdIndex = reader.GetOrdinal("ProductId");
                int productNameIndex = reader.GetOrdinal("Name");
                int productLocationIndex = reader.GetOrdinal("Location");
                int productQuantityIndex = reader.GetOrdinal("Quantity");
                while (reader.Read())
                {
                    var product = new Product()
                    {
                        ProductId = reader.GetInt32(productIdIndex),
                        Name = reader.GetString(productNameIndex),
                        Location = reader.GetString(productLocationIndex),
                        Quantity = reader.GetInt16(productQuantityIndex)
                    };
                    products.Add(product);
                }
            }

            return products;
        }

        private static DataSet GetDataFromDB(DependencyInfo dependencyInfo)
        {
            DataSet dataset;
            string sqlText = string.Empty;

            if (dependencyInfo.IsSQLSelectQueryBased)
                sqlText = GetSQL();

            using (var connection =
               new SqlConnection(GetConnectionString()))
            {
                using (var command =
                    new SqlCommand(sqlText, connection))
                {
                    connection.Open();
                    dataset = new DataSet();
                    var adapter = new SqlDataAdapter(command).Fill(dataset);
                }
            }
            return dataset;
        }

        #endregion

    }


    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
        public int Quantity { get; set; }

        public override bool Equals(object obj)
        {
            var other = obj as Product;
            if (other == this)
                return true;
            if (ReferenceEquals(this, other))
                return true;
            if (this.ProductId == other.ProductId
                && this.Name.Equals(other.Name)
                && this.Location.Equals(other.Location)
                && this.Quantity.Equals(other.Quantity))
                return true;
            else
                return false;
        }

        public override int GetHashCode()
        {
            return (ProductId.ToString() +
                Name +
                Location +
                Quantity.ToString()).GetHashCode();
        }
    }
}
